// 数据包列表页面功能
class PacketsPage {
    constructor() {
        this.currentPage = 1;
        this.pageSize = Config.DEFAULT_PAGE_SIZE;
        this.totalPages = 1;
        this.selectedPacketId = null;
        this.init();
    }

    init() {
        this.bindEvents();
        this.loadPackets();
    }

    bindEvents() {
        // 搜索按钮事件
        document.getElementById('searchBtn').addEventListener('click', () => {
            this.currentPage = 1;
            this.loadPackets();
        });

        // 清空按钮事件
        document.getElementById('clearBtn').addEventListener('click', () => {
            this.clearPackets();
        });

        // 导入按钮事件
        document.getElementById('importBtn').addEventListener('click', () => {
            this.showUploadModal();
        });

        // 流重组按钮事件
        document.getElementById('streamBtn').addEventListener('click', () => {
            const sessionId = document.getElementById('sessionIdFilter').value;
            this.reassembleStream(sessionId);
        });

        // 确认上传按钮事件
        document.getElementById('confirmUploadBtn').addEventListener('click', () => {
            this.uploadFile();
        });

        // 回车键搜索
        document.addEventListener('keypress', (event) => {
            if (event.key === 'Enter') {
                this.currentPage = 1;
                this.loadPackets();
            }
        });
    }

    // 显示上传模态框
    showUploadModal() {
        // 重置表单
        document.getElementById('uploadForm').reset();
        document.getElementById('uploadProgress').style.display = 'none';
        document.getElementById('uploadStatus').innerHTML = '';
        document.getElementById('uploadProgressBar').style.width = '0%';

        // 显示模态框
        new bootstrap.Modal(document.getElementById('uploadModal')).show();
    }

    // 上传文件
    async uploadFile() {
        const fileInput = document.getElementById('pcapFile');
        const confirmBtn = document.getElementById('confirmUploadBtn');
        const progressBar = document.getElementById('uploadProgressBar');
        const progressContainer = document.getElementById('uploadProgress');
        const statusDiv = document.getElementById('uploadStatus');

        if (!fileInput.files || fileInput.files.length === 0) {
            statusDiv.innerHTML = '<div class="alert alert-warning">请选择要上传的文件</div>';
            return;
        }

        const file = fileInput.files[0];
        const formData = new FormData();
        formData.append('file', file);

        // 禁用按钮并显示进度条
        confirmBtn.disabled = true;
        progressContainer.style.display = 'block';
        statusDiv.innerHTML = '<div class="alert alert-info">正在上传文件...</div>';

        try {
            const response = await fetch('/api/packets/upload', {
                method: 'POST',
                body: formData
            });

            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(errorText || `上传失败: ${response.status}`);
            }

            const result = await response.json();

            // 更新进度条到100%
            progressBar.style.width = '100%';
            progressBar.classList.add('bg-success');

            statusDiv.innerHTML = `<div class="alert alert-success">
                <strong>导入成功!</strong><br>
                成功导入 ${result.importedCount} 个数据包
            </div>`;

            // 2秒后关闭模态框并刷新数据
            setTimeout(() => {
                bootstrap.Modal.getInstance(document.getElementById('uploadModal')).hide();
                this.loadPackets();
            }, 2000);

        } catch (error) {
            console.error('文件上传失败:', error);
            progressBar.classList.add('bg-danger');
            statusDiv.innerHTML = `<div class="alert alert-danger">
                <strong>导入失败!</strong><br>
                ${error.message}
            </div>`;
        } finally {
            confirmBtn.disabled = false;
        }
    }

	// 加载数据包列表
	async loadPackets() {
		const protocol = document.getElementById('protocolFilter').value;
		const sourceIp = document.getElementById('sourceIpFilter').value.trim();
		const destIp = document.getElementById('destIpFilter').value.trim();
		const sourcePort = document.getElementById('sourcePortFilter').value.trim();
		const destPort = document.getElementById('destPortFilter').value.trim();
		const sessionId = document.getElementById('sessionIdFilter').value.trim();
		const bidirectional = document.getElementById('bidirectionalFilter').checked;

		const params = new URLSearchParams({
			pageNo: this.currentPage,
			pageSize: this.pageSize
		});

		if (protocol) params.append('protocol', protocol);
		if (sessionId) params.append('sessionId', sessionId);

		// 添加双向过滤参数
		if (bidirectional) {
			params.append('bidirectional', 'true');

			// 在双向模式下，IP和端口参数可以匹配源或目标
			if (sourceIp) params.append('sourceIp', sourceIp);
			if (destIp) params.append('destinationIp', destIp);
			if (sourcePort) params.append('sourcePort', sourcePort);
			if (destPort) params.append('destinationPort', destPort);

			// 添加双向搜索标志
			if (sourceIp || destIp || sourcePort || destPort) {
				params.append('bidirectionalSearch', 'true');
			}
		} else {
			// 单向模式，保持原有逻辑
			if (sourceIp) params.append('sourceIp', sourceIp);
			if (destIp) params.append('destinationIp', destIp);
			if (sourcePort) params.append('sourcePort', sourcePort);
			if (destPort) params.append('destinationPort', destPort);
		}

		Utils.showLoading('packetTableBody', '加载数据包...');

		try {
			const data = await Utils.apiRequest(`/api/packets?${params.toString()}`);
			this.displayPackets(data.records);
			this.updatePagination(data.current, data.pages, data.total);
		} catch (error) {
			console.error('加载数据包失败:', error);
			Utils.showError('packetTableBody', '加载失败');
		}
	}

    async clearPackets() {
        const confirmed = confirm('确定要清空所有数据包记录吗？此操作不可恢复！');
        if (!confirmed) {
            return;
        }
        document.getElementById('sessionIdFilter').value = '';

        try {
            const response = await fetch('/api/packets', {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json',
                }
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            // 清空成功，重新加载数据
            Notification.success('数据包记录已清空');
            this.currentPage = 1;
            this.loadPackets();

        } catch (error) {
            console.error('清空数据包失败:', error);
            Notification.error('清空数据包失败: ' + error.message);
        }
    }

async reassembleStream(sessionId) {
    // 如果没有传入sessionId，提示用户输入
    if (!sessionId) {
        sessionId = prompt('请输入要重组的会话ID:');
        if (!sessionId) {
            return;
        }
    }

    const confirmed = confirm(`确定要重组会话 ${sessionId} 的流吗？`);
    if (!confirmed) {
        return;
    }

    try {
        const response = await fetch(`/api/session/reassemble/${sessionId}`, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            }
        });

        if (response.status === 204) {
            Notification.info('该会话没有可重组的数据');
            return;
        }

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const result = await response.json(); // 改为解析JSON响应

        // 显示重组结果
        this.showReassemblyResult(result, sessionId);

        Notification.success(`会话 ${sessionId} 重组完成`);

    } catch (error) {
        console.error('重组流失败:', error);
        Notification.error('重组流失败: ' + error.message);
    }
}
// 显示重组结果的辅助方法
showReassemblyResult(result, sessionId) {
    const { streamData, contentType, fileName, fileSize } = result;
    // 创建一个模态框来显示重组结果
    const modalHtml = `
        <div class="modal fade" id="reassemblyModal" tabindex="-1">
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">流重组结果 - 会话 ${sessionId}</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body">
                        ${fileName ? `
                        <div class="alert alert-info mb-3">
                            <strong>检测到文件:</strong> ${fileName} (${this.formatFileSize(fileSize)})
                            <button type="button" class="btn btn-sm btn-success float-end" onclick="packetsPage.exportFile('${sessionId}', '${fileName}')">
                                <i class="bi bi-download"></i> 导出文件
                            </button>
                        </div>
                        ` : ''}

                        <div class="mb-3">
                            <strong>内容类型:</strong> ${contentType || '未知'}
                        </div>

                        <div class="content-preview">
                            <h6>内容预览:</h6>
                            <pre style="max-height: 400px; overflow-y: auto; background: #f8f9fa; padding: 10px; border-radius: 4px;">${this.escapeHtml(this.truncateContent(streamData))}</pre>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
                        <button type="button" class="btn btn-primary" onclick="packetsPage.copyReassemblyResult()">复制内容</button>
                        ${fileName ? `
                        <button type="button" class="btn btn-success" onclick="packetsPage.exportFile('${sessionId}', '${fileName}')">
                            <i class="bi bi-download"></i> 导出文件
                        </button>
                        ` : ''}
                    </div>
                </div>
            </div>
        </div>
    `;

    // 移除已存在的模态框
    const existingModal = document.getElementById('reassemblyModal');
    if (existingModal) {
        existingModal.remove();
    }

    // 添加新的模态框到页面
    document.body.insertAdjacentHTML('beforeend', modalHtml);

    // 显示模态框
    const modal = new bootstrap.Modal(document.getElementById('reassemblyModal'));
    modal.show();
}

// 截断内容用于预览
truncateContent(content, maxLength = 10240) {
    if (!content) return '';
    if (content.length <= maxLength) return content;
    return content.substring(0, maxLength) + '\n\n... (内容已截断，完整内容请导出文件查看)';
}

// 格式化文件大小
formatFileSize(bytes) {
    if (!bytes) return '0 B';
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(1024));
    return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
}

// 导出文件
async exportFile(sessionId, fileName) {
    try {
        const response = await fetch(`/api/session/export/${sessionId}`, {
            method: 'GET'
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        // 获取Blob数据
        const blob = await response.blob();

        // 创建下载链接
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = fileName || `session_${sessionId}.bin`;
        document.body.appendChild(a);
        a.click();

        // 清理
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);

        Notification.success(`文件 ${fileName} 导出成功`);

    } catch (error) {
        console.error('导出文件失败:', error);
        Notification.error('导出文件失败: ' + error.message);
    }
}

// HTML转义辅助方法
escapeHtml(unsafe) {
    return unsafe
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

// 复制重组结果到剪贴板
copyReassemblyResult() {
    const preElement = document.querySelector('#reassemblyModal pre');
    const textToCopy = preElement.textContent;

    navigator.clipboard.writeText(textToCopy).then(() => {
        Notification.success('内容已复制到剪贴板');
    }).catch(err => {
        console.error('复制失败:', err);
        Notification.error('复制失败');
    });
}

    // 显示数据包列表
    displayPackets(packets) {
        const tbody = document.getElementById('packetTableBody');

        if (!packets || packets.length === 0) {
            Utils.showEmpty(tbody, '暂无数据', 6);
            return;
        }

        tbody.innerHTML = packets.map(packet => `
            <tr class="packet-row ${this.selectedPacketId === packet.id ? 'selected' : ''}"
                onclick="packetsPage.togglePacketDetail(${packet.id},'${packet.sessionId || ''}')">
                <td>${Utils.formatTimeWithNanoseconds(packet.captureTime)}</td>
                <td>
                    <span class="badge ${Utils.getProtocolBadgeClass(packet.protocol)} protocol-badge">
                        ${packet.protocol}
                    </span>
                </td>
                <td>${packet.sessionId}</td>
                <td>${packet.sourceIp || ''}${packet.sourcePort ? ':' + packet.sourcePort : ''}</td>
                <td>${packet.destinationIp || ''}${packet.destinationPort ? ':' + packet.destinationPort : ''}</td>
                <td>${packet.packetLength || 0} B</td>
                <td class="text-truncate-custom" title="${packet.summary || ''}">${packet.summary || ''}</td>
                <td class="text-nowrap">
                    <button class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation(); packetsPage.togglePacketDetail(${packet.id})">
                        <i class="bi bi-eye"></i> 详情
                    </button>
                </td>
                <td>${packet.payload}</td>
            </tr>
        `).join('');
    }

    // 更新分页
    updatePagination(current, pages, total) {
        this.currentPage = current;
        this.totalPages = pages;

        const pagination = document.getElementById('pagination');
        let html = '';

        // 上一页
        html += `<li class="page-item ${current <= 1 ? 'disabled' : ''}">
            <a class="page-link" href="#" onclick="${current > 1 ? 'packetsPage.goToPage(' + (current - 1) + ')' : 'return false;'}">上一页</a>
        </li>`;

        // 页码
        const startPage = Math.max(1, current - 2);
        const endPage = Math.min(pages, current + 2);

        if (startPage > 1) {
            html += `<li class="page-item"><a class="page-link" href="#" onclick="packetsPage.goToPage(1)">1</a></li>`;
            if (startPage > 2) {
                html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
            }
        }

        for (let i = startPage; i <= endPage; i++) {
            html += `<li class="page-item ${i === current ? 'active' : ''}">
                <a class="page-link" href="#" onclick="packetsPage.goToPage(${i})">${i}</a>
            </li>`;
        }

        if (endPage < pages) {
            if (endPage < pages - 1) {
                html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
            }
            html += `<li class="page-item"><a class="page-link" href="#" onclick="packetsPage.goToPage(${pages})">${pages}</a></li>`;
        }

        // 下一页
        html += `<li class="page-item ${current >= pages ? 'disabled' : ''}">
            <a class="page-link" href="#" onclick="${current < pages ? 'packetsPage.goToPage(' + (current + 1) + ')' : 'return false;'}">下一页</a>
        </li>`;

        pagination.innerHTML = html;
    }

    // 跳转到指定页面
    goToPage(page) {
        this.currentPage = page;
        this.loadPackets();
    }

    // 切换数据包详情显示
    togglePacketDetail(packetId, sessionId) {
        if (this.selectedPacketId === packetId) {
            // 如果点击的是当前选中的包，则关闭模态框
            this.hidePacketDetail();
        } else {
            if (sessionId) {
                document.getElementById('sessionIdFilter').value = sessionId;
            }
            // 显示新的数据包详情
            this.showPacketDetail(packetId);
        }
    }

    // 显示数据包详情
    async showPacketDetail(packetId) {
        try {
            // 先隐藏当前详情面板
            this.hidePacketDetail();

            // 设置当前选中的数据包ID
            this.selectedPacketId = packetId;

            // 获取数据包信息（改为调用后端真实数据）
            const packet = await Utils.apiRequest(`/api/packets/${packetId}`);

            if (!packet) {
                throw new Error('数据包不存在');
            }

            // 更新表格行的选中状态
            this.updateTableRowSelection();

            // 生成详情内容
            document.getElementById('protocolContent').innerHTML = this.generateProtocolLayersHtml(packet);
            document.getElementById('hexContent').innerHTML = this.generateHexViewHtml(packet);
            document.getElementById('rawContent').innerHTML = this.generateRawDataHtml(packet);

            // 以模态框形式显示详情
            if (!this.detailModal) {
                this.detailModal = new bootstrap.Modal(document.getElementById('packetDetailModal'));
            }
            this.detailModal.show();

        } catch (error) {
            console.error('显示数据包详情失败:', error);
            Notification.error('显示数据包详情失败: ' + error.message);
            // 隐藏详情面板
            this.hidePacketDetail();
        }
    }

    // 隐藏数据包详情
    hidePacketDetail() {
        this.selectedPacketId = null;
        this.updateTableRowSelection();
        if (this.detailModal) {
            this.detailModal.hide();
        }
    }

    // 更新表格行的选中状态
    updateTableRowSelection() {
        const rows = document.querySelectorAll('.packet-row');
        rows.forEach(row => {
            row.classList.remove('selected');
            // 通过检查onclick属性中的packetId来匹配选中状态
            const onclickStr = row.getAttribute('onclick') || '';
            if (this.selectedPacketId && onclickStr.includes(`togglePacketDetail(${this.selectedPacketId})`)) {
                row.classList.add('selected');
            }
        });
    }

    // 生成模拟数据包数据
    generateMockPacketData(packetId) {
        const protocols = ['UDP', 'TCP', 'HTTP', 'DNS', 'DHCP'];
        const protocol = protocols[packetId % protocols.length];

        return {
            id: packetId,
            protocol: protocol,
            captureTime: new Date().toISOString(),
            packetLength: 150 + (packetId * 10),
            sourceIp: '192.168.1.100',
            destinationIp: '8.8.8.8',
            sourcePort: 12345 + packetId,
            destinationPort: protocol === 'DNS' ? 53 : (protocol === 'HTTP' ? 80 : 443),
            ethernetFrame: {
                destinationMac: '00:1B:44:11:3A:B7',
                sourceMac: '00:0C:29:56:12:34',
                etherType: '0x0800 (IPv4)',
                length: 150
            },
            ipLayer: {
                version: '4',
                headerLength: '20 bytes',
                totalLength: '150 bytes',
                identification: '0x1234',
                flags: '0x4000 (Don\'t Fragment)',
                fragmentOffset: '0',
                ttl: '64',
                protocol: protocol === 'UDP' ? '17 (UDP)' : '6 (TCP)',
                headerChecksum: '0xABCD',
                sourceIp: '192.168.1.100',
                destinationIp: '8.8.8.8'
            },
            transportLayer: protocol === 'UDP' ? {
                sourcePort: 12345 + packetId,
                destinationPort: 53,
                length: '130 bytes',
                checksum: '0xEF12'
            } : {
                sourcePort: 12345 + packetId,
                destinationPort: 80,
                sequenceNumber: '0x12345678',
                acknowledgmentNumber: '0x87654321',
                headerLength: '20 bytes',
                flags: '0x018 (PSH, ACK)',
                windowSize: '8192',
                checksum: '0x3456',
                urgentPointer: '0'
            },
            applicationLayer: protocol === 'DNS' ? {
                transactionId: '0x1234',
                flags: '0x0100 (Standard Query)',
                questions: '1',
                answerRRs: '0',
                authorityRRs: '0',
                additionalRRs: '0',
                queries: 'example.com',
                queryType: 'A (Host Address)',
                queryClass: 'IN (Internet)'
            } : protocol === 'HTTP' ? {
                method: 'GET',
                url: '/api/data',
                version: 'HTTP/1.1',
                host: 'api.example.com',
                userAgent: 'Mozilla/5.0',
                accept: 'application/json'
            } : null,
            rawData: this.generateMockHexData(150)
        };
    }

    // 生成模拟十六进制数据
    generateMockHexData(length) {
        const hexChars = '0123456789ABCDEF';
        let result = '';
        for (let i = 0; i < length; i++) {
            result += hexChars[Math.floor(Math.random() * 16)];
            result += hexChars[Math.floor(Math.random() * 16)];
            if (i < length - 1) result += ' ';
            if ((i + 1) % 16 === 0) result += '\n';
        }
        return result;
    }

    // 将后端 hexData 规范化为按16字节分组的数组
    normalizeHexLines(packet) {
        if (!packet) return [];

        let hex = packet.hexData ? String(packet.hexData) : '';
        if (!hex && packet.payload) {
            try {
                const bytes = new TextEncoder().encode(String(packet.payload));
                hex = Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(' ');
            } catch (e) {
                hex = '';
            }
        }
        hex = hex.replace(/[^0-9a-fA-F]/g, ' ').replace(/\s+/g, ' ').trim();
        if (!hex) return [];
        const tokens = hex.split(' ').filter(Boolean).map(t => t.padStart(2, '0').slice(-2).toUpperCase());
        const lines = [];
        for (let i = 0; i < tokens.length; i += 16) {
            lines.push(tokens.slice(i, i + 16));
        }
        return lines;
    }

    // 生成协议分层HTML（使用后端真实字段）
    generateProtocolLayersHtml(packet) {
        const basic = `
            <div class="protocol-layer">
                <div class="protocol-layer-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
                    <span><i class="bi bi-info-circle"></i> 基本信息</span>
                    <i class="bi bi-chevron-down"></i>
                </div>
                <div class="protocol-layer-content" style="display: block;">
                    <div class="protocol-field"><span class="field-name">协议:</span><span class="field-value">${packet.protocol || ''}</span></div>
                    <div class="protocol-field"><span class="field-name">捕获时间:</span><span class="field-value">${Utils.formatTimeWithNanoseconds(packet.captureTime)}</span></div>
                    <div class="protocol-field"><span class="field-name">数据包长度:</span><span class="field-value">${packet.packetLength || 0} bytes</span></div>
                    <div class="protocol-field"><span class="field-name">会话ID:</span><span class="field-value">${packet.sessionId || ''}</span></div>
                </div>
            </div>`;

        const ipTransport = `
            <div class="protocol-layer">
                <div class="protocol-layer-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
                    <span><i class="bi bi-globe"></i> 网络/传输层</span>
                    <i class="bi bi-chevron-down"></i>
                </div>
                <div class="protocol-layer-content" style="display: block;">
                    <div class="protocol-field"><span class="field-name">源地址:</span><span class="field-value">${packet.sourceIp || ''}${packet.sourcePort ? ':' + packet.sourcePort : ''}</span></div>
                    <div class="protocol-field"><span class="field-name">目标地址:</span><span class="field-value">${packet.destinationIp || ''}${packet.destinationPort ? ':' + packet.destinationPort : ''}</span></div>
                    ${packet.tcpFlags ? `<div class=\"protocol-field\"><span class=\"field-name\">TCP标志位:</span><span class=\"field-value\">${packet.tcpFlags}</span></div>` : ''}
                    ${packet.tcpSeqNumber != null ? `<div class=\"protocol-field\"><span class=\"field-name\">TCP序列号:</span><span class=\"field-value\">${packet.tcpSeqNumber}</span></div>` : ''}
                    ${packet.tcpAckNumber != null ? `<div class=\"protocol-field\"><span class=\"field-name\">TCP确认号:</span><span class=\"field-value\">${packet.tcpAckNumber}</span></div>` : ''}
                </div>
            </div>`;

        const http = (packet.httpMethod || packet.httpUrl || packet.httpStatus || packet.httpHeaders || packet.httpBody) ? `
            <div class="protocol-layer">
                <div class="protocol-layer-header" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">
                    <span><i class="bi bi-application"></i> 应用层 (HTTP)</span>
                    <i class="bi bi-chevron-down"></i>
                </div>
                <div class="protocol-layer-content" style="display: block;">
                    ${packet.httpMethod ? `<div class=\"protocol-field\"><span class=\"field-name\">方法:</span><span class=\"field-value\">${packet.httpMethod}</span></div>` : ''}
                    ${packet.httpUrl ? `<div class=\"protocol-field\"><span class=\"field-name\">URL:</span><span class=\"field-value\">${packet.httpUrl}</span></div>` : ''}
                    ${packet.httpStatus != null ? `<div class=\"protocol-field\"><span class=\"field-name\">状态码:</span><span class=\"field-value\">${packet.httpStatus}</span></div>` : ''}
                    ${packet.httpHeaders ? `<div class=\"protocol-field\"><span class=\"field-name\">Headers:</span><span class=\"field-value\"><pre class=\"mb-0\">${Utils.escapeHtml(packet.httpHeaders)}</pre></span></div>` : ''}
                    ${packet.httpBody ? `<div class=\"protocol-field\"><span class=\"field-name\">Body:</span><span class=\"field-value\"><pre class=\"mb-0\">${Utils.escapeHtml(packet.httpBody)}</pre></span></div>` : ''}
                </div>
            </div>` : '';

        return `${basic}${ipTransport}${http}`;
    }

    // 生成十六进制视图HTML（来源于 hexData 或 payload）
    generateHexViewHtml(packet) {
        const lines = this.normalizeHexLines(packet);
        if (!lines.length) {
            return '<div class="text-muted">无十六进制数据</div>';
        }
        let html = '';
        lines.forEach((bytes, index) => {
            const offset = (index * 16).toString(16).padStart(8, '0').toUpperCase();
            const ascii = bytes.map(byte => {
                const value = parseInt(byte, 16);
                return (value >= 32 && value <= 126) ? String.fromCharCode(value) : '.';
            }).join('');
            html += `
                <div class="hex-line">
                    <div class="hex-offset">${offset}:</div>
                    <div class="hex-bytes">${bytes.map(b => `<span class="byte">${b}</span>`).join(' ')}</div>
                    <div class="hex-ascii">${ascii.split('').map(c => `<span class="char">${c}</span>`).join('')}</div>
                </div>`;
        });
        return html;
    }

    // 生成原始数据HTML（真实字段罗列）
    generateRawDataHtml(packet) {
        const lines = [];
        lines.push(`数据包ID: ${packet.id}`);
        lines.push(`协议: ${packet.protocol || ''}`);
        lines.push(`捕获时间: ${Utils.formatTimeWithNanoseconds(packet.captureTime)}`);
        lines.push(`数据包长度: ${packet.packetLength || 0} 字节`);
        lines.push('');
        lines.push(`源: ${packet.sourceIp || ''}${packet.sourcePort ? ':' + packet.sourcePort : ''}`);
        lines.push(`目标: ${packet.destinationIp || ''}${packet.destinationPort ? ':' + packet.destinationPort : ''}`);
        if (packet.tcpFlags) lines.push(`TCP标志位: ${packet.tcpFlags}`);
        if (packet.tcpSeqNumber != null) lines.push(`TCP序列号: ${packet.tcpSeqNumber}`);
        if (packet.tcpAckNumber != null) lines.push(`TCP确认号: ${packet.tcpAckNumber}`);
        lines.push('');
        if (packet.httpMethod || packet.httpUrl || packet.httpStatus) {
            lines.push('=== HTTP ===');
            if (packet.httpMethod) lines.push(`方法: ${packet.httpMethod}`);
            if (packet.httpUrl) lines.push(`URL: ${packet.httpUrl}`);
            if (packet.httpStatus != null) lines.push(`状态码: ${packet.httpStatus}`);
            if (packet.httpHeaders) lines.push(`Headers:\n${packet.httpHeaders}`);
            if (packet.httpBody) lines.push(`Body:\n${packet.httpBody}`);
            lines.push('');
        }
        const hexJoined = this.normalizeHexLines(packet).map(arr => arr.join(' ')).join('\n');
        if (hexJoined) {
            lines.push('=== 原始十六进制数据 ===');
            lines.push(hexJoined);
        } else if (packet.payload) {
            lines.push('=== 原始内容 ===');
            lines.push(String(packet.payload));
        }
        return `
            <div class="raw-content"><pre class="mb-0">${Utils.escapeHtml(lines.join('\n'))}</pre></div>
        `;
    }
}

// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
    window.packetsPage = new PacketsPage();
});